<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Rich Text → Markdown (No React)</title>
  <style>
    :root {
      --bg: #ffffff;
      --card: #ffffff;
      --muted: #6b7280;
      --text: #111111;
      --accent: #4f8cff;
      --border: #e5e7eb;
    }
    * { box-sizing: border-box; }
    html, body { height: 100%; }
    body {
      margin: 0;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
      background: var(--bg);
      color: var(--text);
    }
    .wrap {
      max-width: 900px;
      margin: 0 auto;
      padding: 16px;
      display: flex;
      flex-direction: column;
      gap: 12px;
    }
    header h1 { font-size: 1.25rem; margin: 0 0 4px; }
    header p { margin: 0; color: var(--muted); font-size: 0.95rem; }

    .panel {
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 14px;
      padding: 12px;
    }

    .grid {
      display: grid;
      grid-template-columns: 1fr;
      gap: 12px;
    }
    @media (min-width: 840px) {
      .grid { grid-template-columns: 1fr 1fr; }
    }

    .label {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      font-size: 0.9rem;
      color: var(--muted);
      margin-bottom: 8px;
    }

    .dropzone {
      border: 1px dashed var(--border);
      border-radius: 12px;
      min-height: 180px;
      padding: 12px;
      background: #ffffff;
      outline: none;
      overflow: auto;
    }
    .dropzone:focus { border-color: var(--accent); box-shadow: 0 0 0 2px #4f8cff22; }
    .placeholder { color: #6b7280; }

    textarea.output {
      width: 100%;
      min-height: 220px;
      resize: vertical;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      background: #ffffff;
      border: 1px solid var(--border);
      border-radius: 12px;
      color: var(--text);
      padding: 12px;
      line-height: 1.35;
    }

    .btnrow {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }

    button {
      appearance: none;
      border: 1px solid var(--border);
      background: #f3f4f6; /* light gray for legibility on white */
      color: var(--text);
      padding: 10px 14px;
      border-radius: 12px;
      font-size: 0.95rem;
    }
    button.primary { border-color: transparent; background: var(--accent); color: white; }
    button:active { transform: translateY(1px); }

    .hint { color: var(--muted); font-size: 0.85rem; }
    .toast { font-size: 0.9rem; color: #b3ffbe; display: none; }
  </style>
</head>
<body>
  <div class="wrap">
    <header>
      <h1>Rich Text → Markdown (No React)</h1>
      <p>Paste formatted text on mobile or desktop. Outputs Markdown with <strong>bold</strong>, <em>italic</em>, inline <a href="#">links</a>, paragraph breaks, and nested lists.</p>
    </header>

    <div class="grid">
      <section class="panel">
        <div class="label">
          <span>1) Paste rich text below</span>
          <span class="hint">Tip: long‑press → Paste</span>
        </div>
        <div id="input" class="dropzone" contenteditable="true" role="textbox" aria-label="Paste rich text here" data-placeholder="Paste here…"></div>
      </section>

      <section class="panel">
        <div class="label">
          <span>2) Markdown output</span>
          <span class="toast" id="toast">Copied ✓</span>
        </div>
        <textarea id="output" class="output" spellcheck="false" aria-label="Markdown output" placeholder="Your Markdown will appear here…"></textarea>
        <div class="btnrow" style="margin-top:10px;">
          <button id="copyBtn" type="button">Copy</button>
          <button id="quoteBtn" type="button" style="display:none;">Quote this</button>
        </div>
        <p class="hint" style="margin-top:10px;">Supports <strong>bold</strong>, <em>italic</em>, inline links, lists (including nested), line breaks, and paragraph breaks.</p>
      </section>
    </div>

    <section class="panel">
      <div class="label" style="margin-bottom:0;">
        <span>How it works</span>
      </div>
      <p class="hint" style="margin-top:6px;">When you paste, the app reads the underlying HTML, safely parses it, and maps <code>&lt;strong&gt;</code>/<code>&lt;em&gt;</code>/<code>&lt;a&gt;</code>/<code>&lt;p&gt;</code>/<code>&lt;br&gt;</code> to Markdown.</p>
    </section>
  </div>

  <script>
    const input = document.getElementById('input');
    const output = document.getElementById('output');
    const copyBtn = document.getElementById('copyBtn');
    const quoteBtn = document.getElementById('quoteBtn');
    const toast = document.getElementById('toast');

    // Placeholder behavior for contenteditable
    function updatePlaceholder() {
      if (!input.textContent.trim()) {
        input.classList.add('placeholder');
        input.setAttribute('data-empty', 'true');
        input.innerHTML = '<span class="placeholder">' + (input.getAttribute('data-placeholder') || 'Paste here…') + '</span>';
        placeCaretAtEnd(input);
      }
    }

    function clearPlaceholder() {
      if (input.getAttribute('data-empty') === 'true') {
        input.innerHTML = '';
        input.classList.remove('placeholder');
        input.removeAttribute('data-empty');
      }
    }

    function placeCaretAtEnd(el) {
      el.focus();
      if (typeof window.getSelection != "undefined"
          && typeof document.createRange != "undefined") {
        const range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        const sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }

    function escapeText(text) {
      return text
        .replace(/\\/g, "\\\\")
        .replace(/\*/g, "\\*")
        .replace(/_/g, "\\_")
        .replace(/\[/g, "\\[")
        .replace(/\]/g, "\\]")
        .replace(/\(/g, "\\(")
        .replace(/\)/g, "\\)")
        .replace(/`/g, "\\`");
    }

    function safeHref(href) {
      if (!href) return '';
      const h = href.trim();
      if (/^(https?:|mailto:|tel:)/i.test(h)) return h;
      return '';
    }

    function walk(node, state) {
      if (!state) {
        state = { listStack: [] };
      }
      if (node.nodeType === Node.TEXT_NODE) {
        return escapeText(node.nodeValue || '');
      }
      if (node.nodeType !== Node.ELEMENT_NODE) return '';
      const tag = node.tagName;
      if (tag === 'UL' || tag === 'OL') {
        return processList(node, state, tag === 'OL');
      }
      let inner = Array.from(node.childNodes).map(child => walk(child, state)).join('');
      // Trim inside formatting wrappers to avoid things like ** bold **
      const trimInner = () => inner.replace(/^\s+|\s+$/g, '');

      switch (tag) {
        case 'BR':
          return '\n';
        case 'P':
          return inner.trim() + '\n\n';
        case 'DIV':
        case 'SECTION':
        case 'ARTICLE':
        case 'HEADER':
        case 'FOOTER':
          // Treat block containers as paragraphs
          return inner.trim() ? (inner.trim() + '\n\n') : '';
        case 'STRONG':
        case 'B':
          return trimInner() ? `**${trimInner()}**` : '';
        case 'EM':
        case 'I':
          return trimInner() ? `*${trimInner()}*` : '';
        case 'A':
          const href = safeHref(node.getAttribute('href'));
          const text = inner || href;
          return href ? `[${text}](${href})` : text;
        case 'H1':
        case 'H2':
        case 'H3':
        case 'H4':
        case 'H5':
        case 'H6':
          return inner.trim() + '\n\n';
        case 'SPAN':
          return inner;
        case 'LI':
          return processListItem(node, state);
        default:
          return inner;
      }
    }

    function processList(node, state, ordered) {
      let startIndex = 0;
      if (ordered) {
        const startAttr = parseInt(node.getAttribute('start') || '1', 10);
        if (!Number.isNaN(startAttr)) {
          startIndex = startAttr - 1;
        }
      }
      state.listStack.push({ type: ordered ? 'ol' : 'ul', index: startIndex });
      const items = [];
      for (const child of Array.from(node.children)) {
        if (child.tagName === 'LI') {
          items.push(processListItem(child, state));
        } else {
          const content = walk(child, state);
          if (content.trim()) {
            items.push(content);
          }
        }
      }
      state.listStack.pop();
      const listContent = items.filter(Boolean).join('\n');
      return listContent ? listContent + '\n\n' : '';
    }

    function processListItem(node, state) {
      const current = state.listStack[state.listStack.length - 1];
      if (!current) {
        return Array.from(node.childNodes).map(child => walk(child, state)).join('').trim();
      }

      if (current.type === 'ol') {
        const valueAttr = parseInt(node.getAttribute('value') || '', 10);
        if (!Number.isNaN(valueAttr)) {
          current.index = valueAttr - 1;
        }
        current.index += 1;
      }

      const indent = '  '.repeat(Math.max(0, state.listStack.length - 1));
      const marker = current.type === 'ol' ? `${current.index}. ` : '- ';
      const bullet = indent + marker;
      const hangingIndent = ' '.repeat(bullet.length);

      const blocks = [];
      let buffer = '';

      const flushBuffer = () => {
        if (!buffer) return;
        if (buffer.trim()) {
          blocks.push({ type: 'text', content: buffer.replace(/\n+$/, '') });
        }
        buffer = '';
      };

      for (const child of Array.from(node.childNodes)) {
        if (child.nodeType === Node.ELEMENT_NODE && (child.tagName === 'UL' || child.tagName === 'OL')) {
          flushBuffer();
          const content = walk(child, state).trimEnd();
          if (content) {
            blocks.push({ type: 'list', content });
          }
        } else {
          buffer += walk(child, state);
        }
      }
      flushBuffer();

      if (!blocks.length) {
        return bullet.trimEnd();
      }

      const lines = [];
      const firstBlock = blocks.shift();

      if (firstBlock.type === 'list') {
        lines.push(bullet.trimEnd());
        lines.push(...firstBlock.content.split('\n'));
      } else {
        const textLines = firstBlock.content.split('\n');
        if (textLines.length) {
          lines.push(bullet + textLines[0].trim());
          for (const line of textLines.slice(1)) {
            const trimmed = line.trim();
            lines.push(trimmed ? hangingIndent + trimmed : hangingIndent);
          }
        } else {
          lines.push(bullet.trimEnd());
        }
      }

      for (const block of blocks) {
        if (block.type === 'list') {
          lines.push(...block.content.split('\n'));
        } else {
          const textLines = block.content.split('\n');
          for (const line of textLines) {
            const trimmed = line.trim();
            lines.push(trimmed ? hangingIndent + trimmed : hangingIndent);
          }
        }
      }

      return lines.join('\n');
    }

    function htmlToMarkdown(html) {
      const parser = new DOMParser();
      const doc = parser.parseFromString(html, 'text/html');
      let md = walk(doc.body, { listStack: [] });
      // Normalize newlines: collapse 3+ to 2
      md = md.replace(/\n{3,}/g, '\n\n');
      // Trim extra whitespace around paragraphs
      md = md.replace(/[ \t]+\n/g, '\n');
      return md.trim();
    }

    function updateQuoteVisibility(md) {
      if (!quoteBtn) return;
      if (md && md.trim()) {
        quoteBtn.style.display = '';
      } else {
        quoteBtn.style.display = 'none';
      }
    }

    function convert() {
      const html = input.getAttribute('data-empty') === 'true' ? '' : input.innerHTML;
      const md = htmlToMarkdown(html || '');
      output.value = md;
      updateQuoteVisibility(md);
      return md;
    }

    function showToast() {
      toast.style.display = 'inline';
      setTimeout(() => { toast.style.display = 'none'; }, 1200);
    }

    // Event wiring
    input.addEventListener('focus', clearPlaceholder);
    input.addEventListener('blur', () => { if (!input.textContent.trim()) updatePlaceholder(); });

    input.addEventListener('paste', (e) => {
      // Let the browser paste normally so users see formatting, then convert
      setTimeout(convert, 0);
    });

    input.addEventListener('input', () => {
      // Live conversion on input for immediate feedback
      convert();
    });

    copyBtn.addEventListener('click', async () => {
      try {
        await navigator.clipboard.writeText(output.value || '');
        showToast();
      } catch (err) {
        // Fallback: select the text so the user can copy manually
        output.select();
        showToast();
      }
    });

    if (quoteBtn) {
      quoteBtn.addEventListener('click', () => {
        const text = output.value || '';
        if (!text.trim()) return;
        const quoted = text.split('\n').map(line => {
          if (!line.trim()) {
            return '>';
          }
          return line.startsWith('>') ? line : `> ${line}`;
        }).join('\n');
        output.value = quoted;
      });
    }

    // Init
    updatePlaceholder();
    updateQuoteVisibility('');
  </script>
</body>
</html>
